package com.lhj.xxsc.product.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.xxsc.common.utils.PageUtils;
import com.xxsc.common.utils.Query;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.lhj.xxsc.product.dao.CategoryDao;
import com.lhj.xxsc.product.entity.CategoryEntity;
import com.lhj.xxsc.product.service.CategoryBrandRelationService;
import com.lhj.xxsc.product.service.CategoryService;
import com.lhj.xxsc.product.vo.Catelog2Vo;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;


@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {

//    @Autowired
//    CategoryDao categoryDao;

	@Autowired
	CategoryBrandRelationService categoryBrandRelationService;

	@Autowired
	StringRedisTemplate redisTemplate;

	@Autowired
	RedissonClient redisson;

	@Override
	public PageUtils queryPage(Map<String, Object> params) {
		IPage<CategoryEntity> page = this.page(
				new Query<CategoryEntity>().getPage(params),
				new QueryWrapper<CategoryEntity>()
		);

		return new PageUtils(page);
	}

	@Override
	public List<CategoryEntity> listWithTree() {
		/*
		//1、查出所有分类
		List<CategoryEntity> entities = baseMapper.selectList(null);

		//2、组装成父子的树形结构

		//2.1）、找到所有的一级分类
		List<CategoryEntity> level1Menus = entities.stream().filter(categoryEntity ->
				categoryEntity.getParentCid() == 0
		).map((menu) -> {
			menu.setChildren(getChildrens(menu, entities));
			return menu;
		}).sorted((menu1, menu2) -> {
			return (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort());
		}).collect(Collectors.toList());
		*/

		// 1.查出所有的分类。
		List<CategoryEntity> categoryEntities = baseMapper.selectList(null);
		// 2.组装成父子的树形结构
		// 2.1 查找出所有的一级分类。
		categoryEntities.stream().filter(
				categoryEntity -> categoryEntity.getParentCid() == 0
		).map((menu) -> {
			// 递归去寻找子项
			menu.setChildren(getChildrens(menu, categoryEntities));
			return menu;
		}).sorted((m1, m2) -> {
			return (m1.getSort() == null ? 0 : m1.getSort()) - (m2.getSort() == null ? 0 : m2.getSort());
		}).collect(Collectors.toList());
		return categoryEntities;
	}

	//递归查找所有菜单的子菜单
	private List<CategoryEntity> getChildrens(CategoryEntity root, List<CategoryEntity> all) {

		List<CategoryEntity> children = all.stream().filter(categoryEntity -> {
			return categoryEntity.getParentCid().equals(root.getCatId());
		}).map(categoryEntity -> {
			//1、找到子菜单
			categoryEntity.setChildren(getChildrens(categoryEntity, all));
			return categoryEntity;
		}).sorted((menu1, menu2) -> {
			//2、菜单的排序
			return (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort());
		}).collect(Collectors.toList());
		return children;
	}

	@Override
	public void removeMenuByIds(List<Long> asList) {
		//TODO  1、检查当前删除的菜单，是否被别的地方引用

		//逻辑删除
		baseMapper.deleteBatchIds(asList);
	}

	//[2,25,225]
	@Override
	public Long[] findCatelogPath(Long catelogId) {
		List<Long> paths = new ArrayList<>();
		List<Long> parentPath = findParentPath(catelogId, paths);

		Collections.reverse(parentPath);


		return parentPath.toArray(new Long[parentPath.size()]);
	}

	//225,25,2
	private List<Long> findParentPath(Long catelogId, List<Long> paths) {
		//1、收集当前节点id
		paths.add(catelogId);
		CategoryEntity byId = this.getById(catelogId);
		if (byId.getParentCid() != 0) {
			findParentPath(byId.getParentCid(), paths);
		}
		return paths;

	}

	/**
	 * 级联更新所有关联的数据
	 *
	 * @param category
	 * @CacheEvict:失效模式
	 */
	@CacheEvict(value = "category", key = "'getLevel1Categorys'")
	@Transactional
	@Override
	public void updateCascade(CategoryEntity category) {
		this.updateById(category);
		categoryBrandRelationService.updateCategory(category.getCatId(), category.getName());
	}


	@Override
	/**
	 *  查出一级分类
	 * @return
	 *
	 * 每个需要缓存的数据我们都来指定存放到哪个名字的缓存中。【缓存的分区，需要结合业务来命名】
	 * 表示当前方法的结果需要缓存，如果缓存中有，那么方法不需要调用。如果缓存中没有，会调用方法，最后将方法的结果存入缓存。
	 * 默认行为
	 *  1.如果缓存有，那么方法不会调用。
	 *  2.key是默认生成的，缓存名字，category::simpleKey[](自动生成的key)
	 *  3.value默认是java序列化到redis
	 *  4.默认ttl为-1，永不过期！！！
	 * 自定义：
	 *  1.指定生成缓存使用的key : key 属性指定，接收一个SpEl
	 *      spel
	 *  2.指定缓存的数据存活的时间 : spring.cache.redis.time-to-live=360000
	 *  3.数据保存为json格式
	 *      CacheAutoConfiguration
	 *      src="/static/index ->
	 *  4.spring 不足
	 *      读模式：
	 *      缓存穿透：解决！
	 *      缓存击穿：加锁
	 *      缓存雪崩：加了过期时间
	 *
	 *
	 * // TODO 先按照视频来做。后面我们在考虑别的。现在他其实是默认加了锁的。。。
	 *
	 *
	 *      写模式：
	 *      读写锁
	 *      引入canal
	 *      读多写多，全部都去数据库查
	 *      CacheManager（ReidsCacheManager） Cache（RedisCache）
	 *      总结
	 *      读多写少！才用。他不完美！！
	 */
	@Cacheable(value = {"category"}, key = "#root.methodName", sync = true)
	public List<CategoryEntity> getLevel1Categorys() {
		System.out.println(Thread.currentThread().getId());
		List<CategoryEntity> categoryEntities = baseMapper.selectList(new QueryWrapper<CategoryEntity>().eq("parent_cid", 0));
		return categoryEntities;
	}

	/*String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then\n" +
			"    return redis.call(\"del\",KEYS[1])\n" +
			"else\n" +
			"    return 0\n" +
			"end";*/
	//TODO 大量请求时产生了堆外内存溢出 OutOfDirectMemoryError
	//1.springBoot 2.0 默认使用lettuce 作为操作redis的客户端，使用netty进行网络通信
	//2.lettuce 的bug 导致netty堆外内存溢出 -xmx300m 作为默认堆外内存。
	// 可以通过Dio.netty.maxDirectNemory 去调节最大堆外内存
	// 去升级lettuce客户端。2，切换使用jedis


	@Override
	@Cacheable(value = {"category"}, key = "#root.methodName", sync = true)
	public Map<String, List<Catelog2Vo>> getCatalogJson() {

		/**
		 * 1. 将数据库的查询变为一次查询
		 */
		List<CategoryEntity> selectList = baseMapper.selectList(null);

		// 查出所有1,级分类
		List<CategoryEntity> level1Categorys = getParent_cid(selectList, 0L);
		Map<String, List<Catelog2Vo>> parent_cid = level1Categorys.stream().collect(Collectors.toMap(
				k -> k.getCatId().toString(), v -> {
					// 查到一级分类的所有二级分类
					List<CategoryEntity> categoryEntities = getParent_cid(selectList, v.getCatId());

					// 封装上面的结果
					List<Catelog2Vo> Catelog2Vos = null;
					if (categoryEntities != null) {
						Catelog2Vos = categoryEntities.stream().map(l2 -> {
							Catelog2Vo catelog2Vo = new Catelog2Vo(v.getCatId().toString(), null, l2.getCatId().toString(), l2.getName());

							List<CategoryEntity> level3Catelog = getParent_cid(selectList, l2.getCatId());
							if (level3Catelog != null) {
								List<Catelog2Vo.Catelog3Vo> level3List = level3Catelog.stream().map(
										l3 -> {
											Catelog2Vo.Catelog3Vo catelog3Vo = new Catelog2Vo.Catelog3Vo(l2.getParentCid().toString(), l3.getCatId().toString(), l3.getName());
											return catelog3Vo;
										}
								).collect(Collectors.toList());
								catelog2Vo.setCatalog3List(level3List);
							}
							return catelog2Vo;
						}).collect(Collectors.toList());


					}

					return Catelog2Vos;
				}
		));
		return parent_cid;
	}

	/**
	 * 查出二级与三级类目
	 *
	 * @return
	 */
	public Map<String, List<Catelog2Vo>> getCatalogJson2() {
		// 给缓存中存放的json 字符串，拿出的json字符串还需要逆转成对象类型：序列化与反序列过程。
//        //缓存改写1：使用map作为本地缓存
//        Map<String, List<Catalog2Vo>> catalogMap = (Map<String, List<Catalog2Vo>>) cache.get("catalogMap");
//        if (catalogMap == null) {
//            catalogMap = getCategoriesDb();
//            cache.put("catalogMap",catalogMap);
//        }
//        return catalogMap;

//        //缓存改写2：使用redis作为本地缓存
//        ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
//        String catalogJson = ops.get("catalogJson");
//        if (StringUtils.isEmpty(catalogJson)) {
//            Map<String, List<Catalog2Vo>> categoriesDb = getCategoriesDb();
//            String toJSONString = JSON.toJSONString(categoriesDb);
//            ops.set("catalogJson",toJSONString);
//            return categoriesDb;
//        }
//        Map<String, List<Catalog2Vo>> listMap = JSON.parseObject(catalogJson, new TypeReference<Map<String, List<Catalog2Vo>>>() {});
//        return listMap;
		/**
		 * 1.空结果缓存：解决缓存穿透
		 * 2.设置过期时间（加随机值）：解决缓存雪崩问题
		 * 3.加锁：解决缓存击穿
		 */


		// 1.加入缓存逻辑，缓存中存在的数据是json字符串。json 可以友好的跨平台

		// 加入缓存逻辑
		String catalogJSON = redisTemplate.opsForValue().get("catalogJSON");
		if (StringUtils.isEmpty(catalogJSON)) {
			Map<String, List<Catelog2Vo>> catalogJsonFromDb = getCatalogJsonFromDbFromLock();
			return catalogJsonFromDb;
		}
		Map<String, List<Catelog2Vo>> result = JSON.parseObject(catalogJSON, new TypeReference<Map<String, List<Catelog2Vo>>>() {
		});
		return result;

	}

	private Map<String, List<Catelog2Vo>> getCatalogJsonFromDbFromLock() {
		// 锁的名字：锁的粒度，越细越快。
		// 具体某个商品，就需要lock-product-id
		RLock lock = redisson.getLock("lock-catalogJson");
		Map<String, List<Catelog2Vo>> CatalogJsonFromDb;
		lock.lock();
		try {
			String catalogJSON = redisTemplate.opsForValue().get("catalogJSON");
			if (StringUtils.isEmpty(catalogJSON)) {
				CatalogJsonFromDb = getCatalogJsonFromDb();
			} else {
				CatalogJsonFromDb = JSON.parseObject(catalogJSON, new TypeReference<Map<String, List<Catelog2Vo>>>() {
				});
			}
		} finally {
			lock.unlock();
		}
		return CatalogJsonFromDb;
	}


	/**
	 * 从数据库中查询我们三级目录
	 *
	 * @return
	 */
	public Map<String, List<Catelog2Vo>> getCatalogJsonFromDb() {

		/**
		 * 1. 将数据库的查询变为一次查询
		 */
		List<CategoryEntity> selectList = baseMapper.selectList(null);

		// 查出所有1,级分类
		List<CategoryEntity> level1Categorys = getParent_cid(selectList, 0L);
		Map<String, List<Catelog2Vo>> parent_cid = level1Categorys.stream().collect(Collectors.toMap(
				k -> k.getCatId().toString(), v -> {
					// 查到一级分类的所有二级分类
					List<CategoryEntity> categoryEntities = getParent_cid(selectList, v.getCatId());

					// 封装上面的结果
					List<Catelog2Vo> Catelog2Vos = null;
					if (categoryEntities != null) {
						Catelog2Vos = categoryEntities.stream().map(l2 -> {
							Catelog2Vo catelog2Vo = new Catelog2Vo(v.getCatId().toString(), null, l2.getCatId().toString(), l2.getName());

							List<CategoryEntity> level3Catelog = getParent_cid(selectList, l2.getCatId());
							if (level3Catelog != null) {
								List<Catelog2Vo.Catelog3Vo> level3List = level3Catelog.stream().map(
										l3 -> {
											Catelog2Vo.Catelog3Vo catelog3Vo = new Catelog2Vo.Catelog3Vo(l2.getCatId().toString(), l3.getCatId().toString(), l3.getName());
											return catelog3Vo;
										}
								).collect(Collectors.toList());
								catelog2Vo.setCatalog3List(level3List);
							}
							return catelog2Vo;
						}).collect(Collectors.toList());


					}

					return Catelog2Vos;
				}
		));
		// 存入缓存
		String catalogJsonFromDbJson = JSON.toJSONString(parent_cid);
		redisTemplate.opsForValue().set("catalogJSON", catalogJsonFromDbJson, 1, TimeUnit.DAYS);
		return parent_cid;
	}

	private List<CategoryEntity> getParent_cid(List<CategoryEntity> selectList, Long parent_cid) {
		List<CategoryEntity> collect = selectList.stream().filter(
				item -> parent_cid.equals(item.getParentCid())

		).collect(Collectors.toList());
		return collect;
	}


}
